//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
/*
 * JFFS2 -- Journalling Flash File System, Version 2.
 *
 * Copyright (C) 2001-2003 Red Hat, Inc.
 * Created by Arjan van de Ven <arjanv@redhat.com>
 *
 * Copyright (C) 2004 Ferenc Havasi <havasi@inf.u-szeged.hu>,
 *                    University of Szeged, Hungary
 *
 * For licensing information, see the file 'LICENCE' in this directory.
 *
 * $Id: compr.cpp,v 1.4 2006/05/16 14:13:32 shijun Exp $
 *
 */

#include "compr.h"
#include "jffs2_misc.h"
#include "linux/slab.h"

/* Actual compression mode */
static int jffs2_compression_mode = JFFS2_COMPR_MODE_NONE;

//#if jffs2_compression_mode != JFFS2_COMPR_MODE_NONE
static DEFINE_SPINLOCK(jffs2_compressor_list_lock);
//#endif

/* Available compressors are on this list */
static LIST_HEAD(jffs2_compressor_list);

/* Statistics for blocks stored without compression */
static uint32_t none_stat_compr_blocks=0,none_stat_decompr_blocks=0,none_stat_compr_size=0;

/* jffs2_compress:
 * @data: Pointer to uncompressed data
 * @cdata: Pointer to returned pointer to buffer for compressed data
 * @datalen: On entry, holds the amount of data available for compression.
 * On exit, expected to hold the amount of data actually compressed.
 * @cdatalen: On entry, holds the amount of space available for compressed
 * data. On exit, expected to hold the actual size of the compressed
 * data.
 *
 * Returns: Lower byte to be stored with data indicating compression type used.
 * Zero is used to show that the data could not be compressed - the 
 * compressed version was actually larger than the original.
 * Upper byte will be used later. (soon)
 *
 * If the cdata buffer isn't large enough to hold all the uncompressed data,
 * jffs2_compress should compress as much as will fit, and should set 
 * *datalen accordingly to show the amount of data which were compressed.
 */
uint16_t jffs2_compress(struct jffs2_sb_info *c, struct jffs2_inode_info *f,
                unsigned char *data_in, unsigned char **cpage_out,
                uint32_t *datalen, uint32_t *cdatalen)
{
    int ret = JFFS2_COMPR_NONE;
    int compr_ret;
    struct jffs2_compressor *thisOne, *best=NULL;
    unsigned char *output_buf = NULL, *tmp_buf;
    uint32_t orig_slen, orig_dlen;
    uint32_t best_slen=0, best_dlen=0;

    switch (jffs2_compression_mode) {
        case JFFS2_COMPR_MODE_NONE:
            break;
        case JFFS2_COMPR_MODE_PRIORITY:
            output_buf = (unsigned char *)kmalloc(*cdatalen,GFP_KERNEL);
            if (!output_buf) {
                printk(KERN_WARNING "JFFS2: No memory for compressor allocation."
                                " Compression failed.\n");
                goto out;
            }
            orig_slen = *datalen;
            orig_dlen = *cdatalen;
            spin_lock(&jffs2_compressor_list_lock);
            list_for_each_entry(thisOne, &jffs2_compressor_list, list)
            {
            /* Skip decompress-only backwards-compatibility and disabled modules */
                if ((!thisOne->compress) || (thisOne->disabled))
                    continue;

                thisOne->usecount++;
                spin_unlock(&jffs2_compressor_list_lock);
                *datalen  = orig_slen;
                *cdatalen = orig_dlen;
                compr_ret = thisOne->compress(data_in, output_buf, datalen,
                                cdatalen, NULL);
                spin_lock(&jffs2_compressor_list_lock);
                thisOne->usecount--;
                if (!compr_ret) {
                    ret = thisOne->compr;
                    thisOne->stat_compr_blocks++;
                    thisOne->stat_compr_orig_size += *datalen;
                    thisOne->stat_compr_new_size  += *cdatalen;
                    break;
                }
            }
            spin_unlock(&jffs2_compressor_list_lock);
            if (ret == JFFS2_COMPR_NONE)
                kfree(output_buf);
            break;
        case JFFS2_COMPR_MODE_SIZE:
            orig_slen = *datalen;
            orig_dlen = *cdatalen;
            spin_lock(&jffs2_compressor_list_lock);
            list_for_each_entry(thisOne, &jffs2_compressor_list, list)
            {
            /* Skip decompress-only backwards-compatibility and disabled modules */
                if ((!thisOne->compress) || (thisOne->disabled))
                    continue;
            /* Allocating memory for output buffer if necessary */
                if ((thisOne->compr_buf_size < orig_dlen)
                        && (thisOne->compr_buf)) {
                    spin_unlock(&jffs2_compressor_list_lock);
                    kfree(thisOne->compr_buf);
                    spin_lock(&jffs2_compressor_list_lock);
                    thisOne->compr_buf_size=0;
                    thisOne->compr_buf=NULL;
                }
                if (!thisOne->compr_buf) {
                    spin_unlock(&jffs2_compressor_list_lock);
                    tmp_buf = (unsigned char *)kmalloc(orig_dlen,GFP_KERNEL);
                    spin_lock(&jffs2_compressor_list_lock);
                    if (!tmp_buf) {
                        printk(KERN_WARNING "JFFS2: No memory for compressor "
                                        "allocation. (%d bytes)\n",orig_dlen);
                        continue;
                    }
                    else {
                        thisOne->compr_buf = tmp_buf;
                        thisOne->compr_buf_size = orig_dlen;
                    }
                }
                thisOne->usecount++;
                spin_unlock(&jffs2_compressor_list_lock);
                *datalen  = orig_slen;
                *cdatalen = orig_dlen;
                compr_ret = thisOne->compress(data_in, thisOne->compr_buf,
                                datalen, cdatalen, NULL);
                spin_lock(&jffs2_compressor_list_lock);
                thisOne->usecount--;
                if (!compr_ret) {
                    if ((!best_dlen) || (best_dlen >*cdatalen)) {
                        best_dlen = *cdatalen;
                        best_slen = *datalen;
                        best = thisOne;
                    }
                }
            }
            if (best_dlen) {
                *cdatalen = best_dlen;
                *datalen  = best_slen;
                output_buf = best->compr_buf;
                best->compr_buf = NULL;
                best->compr_buf_size = 0;
                best->stat_compr_blocks++;
                best->stat_compr_orig_size += best_slen;
                best->stat_compr_new_size  += best_dlen;
                ret = best->compr;
            }
            spin_unlock(&jffs2_compressor_list_lock);
            break;
        default:
            printk(KERN_ERR "JFFS2: unknow compression mode.\n");
    }
out:
    if (ret == JFFS2_COMPR_NONE) {
        *cpage_out = data_in;
        *datalen = *cdatalen;
        none_stat_compr_blocks++;
        none_stat_compr_size += *datalen;
    }
    else {
        *cpage_out = output_buf;
    }
    return ret;
}

int jffs2_decompress(struct jffs2_sb_info *c, struct jffs2_inode_info *f,
                uint16_t comprtype, unsigned char *cdata_in,
                unsigned char *data_out, uint32_t cdatalen, uint32_t datalen)
{
    struct jffs2_compressor *thisOne;
    int ret;

    /* Older code had a bug where it would write non-zero 'usercompr'
       fields. Deal with it. */
    if ((comprtype & 0xff) <= JFFS2_COMPR_ZLIB)
        comprtype &= 0xff;

    switch (comprtype & 0xff) {
        case JFFS2_COMPR_NONE:
        /* This should be special-cased elsewhere, but we might as well deal with it */
            memcpy(data_out, cdata_in, datalen);
            none_stat_decompr_blocks++;
            break;
        case JFFS2_COMPR_ZERO:
            memset(data_out, 0, datalen);
            break;
        default:
            spin_lock(&jffs2_compressor_list_lock);
            list_for_each_entry(thisOne, &jffs2_compressor_list, list)
            {
                if (comprtype == thisOne->compr) {
                    thisOne->usecount++;
                    spin_unlock(&jffs2_compressor_list_lock);
                    ret = thisOne->decompress(cdata_in, data_out, cdatalen,
                                    datalen, NULL);
                    spin_lock(&jffs2_compressor_list_lock);
                    if (ret) {
                        printk(KERN_WARNING "Decompressor \"%s\" returned %d\n",
                                        thisOne->name, ret);
                    }
                    else {
                        thisOne->stat_decompr_blocks++;
                    }
                    thisOne->usecount--;
                    spin_unlock(&jffs2_compressor_list_lock);
                    return ret;
                }
            }
            printk(KERN_WARNING "JFFS2 compression type 0x%02x not available.\n",
                            comprtype);
            spin_unlock(&jffs2_compressor_list_lock);
            return EIO;
    }
    return 0;
}

int jffs2_register_compressor(struct jffs2_compressor *comp)
{
    struct jffs2_compressor *thisOne;

    if (!comp->name) {
        printk(KERN_WARNING "NULL compressor name at registering "
                        "JFFS2 compressor. Failed.\n");
        return -1;
    }
    comp->compr_buf_size=0;
    comp->compr_buf=NULL;
    comp->usecount=0;
    comp->stat_compr_orig_size=0;
    comp->stat_compr_new_size=0;
    comp->stat_compr_blocks=0;
    comp->stat_decompr_blocks=0;
    D1(printk(KERN_DEBUG "Registering JFFS2 compressor \"%s\"\n", comp->name));

    spin_lock(&jffs2_compressor_list_lock);

    list_for_each_entry(thisOne, &jffs2_compressor_list, list)
    {
        if (thisOne->priority < comp->priority) {
            list_add(&comp->list, thisOne->list.prev);
            goto out;
        }
    }
    list_add_tail(&comp->list, &jffs2_compressor_list);
out:
    D2(list_for_each_entry(thisOne, &jffs2_compressor_list, list)
       {
           printk(KERN_DEBUG "Compressor \"%s\", prio %d\n",
                           thisOne->name, thisOne->priority);
       }
    )

    spin_unlock(&jffs2_compressor_list_lock);

    return 0;
}

int jffs2_unregister_compressor(struct jffs2_compressor *comp)
{
    D2(struct jffs2_compressor *thisOne;
    )

    D1(printk(KERN_DEBUG "Unregistering JFFS2 compressor \"%s\"\n", comp->name));

    spin_lock(&jffs2_compressor_list_lock);

    if (comp->usecount) {
        spin_unlock(&jffs2_compressor_list_lock);
        printk(KERN_WARNING "JFFS2: Compressor modul is in use. Unregister failed.\n");
        return -1;
    }
    list_del(&comp->list);

    D2(list_for_each_entry(thisOne, &jffs2_compressor_list, list)
       {
           printk(KERN_DEBUG "Compressor \"%s\", prio %d\n", thisOne->name,
                           thisOne->priority);
       }
    )
    spin_unlock(&jffs2_compressor_list_lock);
    return 0;
}

#ifdef CONFIG_JFFS2_PROC

#define JFFS2_STAT_BUF_SIZE 16000

char *jffs2_list_compressors(void)
{
    struct jffs2_compressor *thisOne;
    char *buf, *act_buf;

    act_buf = buf = kmalloc(JFFS2_STAT_BUF_SIZE,GFP_KERNEL);
    list_for_each_entry(thisOne, &jffs2_compressor_list, list)
    {
        act_buf += sprintf(act_buf, "%10s priority:%d ",
                        thisOne->name, thisOne->priority);
        if ((thisOne->disabled) || (!thisOne->compress))
            act_buf += sprintf(act_buf,"disabled");
        else
            act_buf += sprintf(act_buf,"enabled");
        act_buf += sprintf(act_buf,"\n");
    }
    return buf;
}

char *jffs2_stats(void)
{
    struct jffs2_compressor *thisOne;
    char *buf, *act_buf;

    act_buf = buf = kmalloc(JFFS2_STAT_BUF_SIZE,GFP_KERNEL);

    act_buf += sprintf(act_buf,"JFFS2 compressor statistics:\n");
    act_buf += sprintf(act_buf,"%10s   ","none");
    act_buf += sprintf(act_buf,"compr: %d blocks (%d)  decompr: %d blocks\n",
                    none_stat_compr_blocks,
                       none_stat_compr_size, none_stat_decompr_blocks);
    spin_lock(&jffs2_compressor_list_lock);
    list_for_each_entry(thisOne, &jffs2_compressor_list, list)
    {
        act_buf += sprintf(act_buf,"%10s ",thisOne->name);
        if ((thisOne->disabled) || (!thisOne->compress))
            act_buf += sprintf(act_buf,"- ");
        else
            act_buf += sprintf(act_buf,"+ ");
        act_buf += sprintf(act_buf,"compr: %d blocks (%d/%d)  decompr: %d blocks ",
                        thisOne->stat_compr_blocks, thisOne->stat_compr_new_size,
                        thisOne->stat_compr_orig_size,
                           thisOne->stat_decompr_blocks);
        act_buf += sprintf(act_buf,"\n");
    }
    spin_unlock(&jffs2_compressor_list_lock);

    return buf;
}

char *jffs2_get_compression_mode_name(void)
{
    switch (jffs2_compression_mode) {
        case JFFS2_COMPR_MODE_NONE:
            return "none";
        case JFFS2_COMPR_MODE_PRIORITY:
            return "priority";
        case JFFS2_COMPR_MODE_SIZE:
            return "size";
    }
    return "unkown";
}

int jffs2_set_compression_mode_name(const char *name)
{
    if (!strcmp("none", name)) {
        jffs2_compression_mode = JFFS2_COMPR_MODE_NONE;
        return 0;
    }
    if (!strcmp("priority", name)) {
        jffs2_compression_mode = JFFS2_COMPR_MODE_PRIORITY;
        return 0;
    }
    if (!strcmp("size", name)) {
        jffs2_compression_mode = JFFS2_COMPR_MODE_SIZE;
        return 0;
    }
    return 1;
}

static int jffs2_compressor_Xable(const char *name, int disabled)
{
    struct jffs2_compressor *thisOne;
    spin_lock(&jffs2_compressor_list_lock);
    list_for_each_entry(thisOne, &jffs2_compressor_list, list)
    {
        if (!strcmp(thisOne->name, name)) {
            thisOne->disabled = disabled;
            spin_unlock(&jffs2_compressor_list_lock);
            return 0;
        }
    }
    spin_unlock(&jffs2_compressor_list_lock);
    printk(KERN_WARNING "JFFS2: compressor %s not found.\n",name);
    return 1;
}

int jffs2_enable_compressor_name(const char *name)
{
    return jffs2_compressor_Xable(name, 0);
}

int jffs2_disable_compressor_name(const char *name)
{
    return jffs2_compressor_Xable(name, 1);
}

int jffs2_set_compressor_priority(const char *name, int priority)
{
    struct jffs2_compressor *thisOne,*comp;
    spin_lock(&jffs2_compressor_list_lock);
    list_for_each_entry(thisOne, &jffs2_compressor_list, list)
    {
        if (!strcmp(thisOne->name, name)) {
            thisOne->priority = priority;
            comp = thisOne;
            goto reinsert;
        }
    }
    spin_unlock(&jffs2_compressor_list_lock);
    printk(KERN_WARNING "JFFS2: compressor %s not found.\n",name);
    return 1;
reinsert:
    /* list is sorted in the order of priority, so if
       we change it we have to reinsert it into the
       good place */
    list_del(&comp->list);
    list_for_each_entry(thisOne, &jffs2_compressor_list, list)
    {
        if (thisOne->priority < comp->priority) {
            list_add(&comp->list, thisOne->list.prev);
            spin_unlock(&jffs2_compressor_list_lock);
            return 0;
        }
    }
    list_add_tail(&comp->list, &jffs2_compressor_list);
    spin_unlock(&jffs2_compressor_list_lock);
    return 0;
}

#endif

void jffs2_free_comprbuf(unsigned char *comprbuf, unsigned char *orig)
{
    if (orig != comprbuf)
        kfree(comprbuf);
}

int jffs2_compressors_init(void)
{
    /* Registering compressors */
#ifdef CONFIG_JFFS2_ZLIB
    jffs2_zlib_init();
#endif
#ifdef CONFIG_JFFS2_RTIME

    jffs2_rtime_init();
#endif
#ifdef CONFIG_JFFS2_RUBIN

    jffs2_rubinmips_init();
    jffs2_dynrubin_init();
#endif
    /* Setting default compression mode */
#ifdef CONFIG_JFFS2_CMODE_NONE

    jffs2_compression_mode = JFFS2_COMPR_MODE_NONE;
    D1(printk(KERN_INFO "JFFS2: default compression mode: none\n");
    )
#else
#ifdef CONFIG_JFFS2_CMODE_SIZE
    jffs2_compression_mode = JFFS2_COMPR_MODE_SIZE;
    D1(printk(KERN_INFO "JFFS2: default compression mode: size\n");
    )
#else
    D1(printk(KERN_INFO "JFFS2: default compression mode: priority\n");
    )
#endif
#endif
    return 0;
}

int jffs2_compressors_exit(void)
{
    /* Unregistering compressors */
#ifdef CONFIG_JFFS2_RUBIN
    jffs2_dynrubin_exit();
    jffs2_rubinmips_exit();
#endif
#ifdef CONFIG_JFFS2_RTIME

    jffs2_rtime_exit();
#endif
#ifdef CONFIG_JFFS2_ZLIB

    jffs2_zlib_exit();
#endif

    return 0;
}
